import { throttle } from './loadsh'
import { isMobile } from './regexp'
import type {
    ClientXY,
    DOMRect,
    DATA_EMPTY,
    ObjectType,
    CreateRippleCbParams,
    ElementBoundary,
    ScrollToOptions
} from './types'

/**
 * 判断当前环境是否兼容 getBoundingClientRect 方法
 */
export const isGetBoundingClientRect = (() => {
    if (typeof window === 'undefined' || typeof window.document === 'undefined') return false
    const ele = window.document.createElement('div')
    return typeof ele.getBoundingClientRect === 'function'
})()

/**
 * 获取元素的尺寸/位置
 * @param n 需要获取节点的位置信息
 * @returns {DOMRect | DATA_EMPTY}
 */
export const getBCR = (n: HTMLElement): DOMRect | DATA_EMPTY => {
    if (!n) return null
    if (isGetBoundingClientRect) {
        return n.getBoundingClientRect()
    }
    // 暂无兼容问题
}

/**
 * 获取当前事件的触发点坐标(包含页面滚动的距离)
 * @param e 事件源-触摸/鼠标
 * @returns {ClientXY | null}
 */
export const getClientXY = (e: any): ClientXY | null => {
    if (!e) return null
    return {
        x: (e.clientX || e.touches[0].clientX) + document.documentElement.scrollLeft,
        y: (e.clientY || e.touches[0].clientY) + document.documentElement.scrollTop
    }
}

/**
 * dom 元素添加-水波纹效果
 * @param {HTMLElement} target 触发事件源
 * @param {Function} cb 动态添加节点开始时的回调
 * @param {Event} event 添加该元素上属性
 */
export const createRipple =
    (target: HTMLElement, cb?: (p?: CreateRippleCbParams) => void) => (event: any) => {
        try {
            const { top, left } = target.getBoundingClientRect()
            const radius: number = Math.sqrt(target.clientWidth ** 2 + target.clientHeight ** 2) / 2
            const size: number = radius * 2
            const centerX: number = (target.clientWidth - radius * 2) / 2
            const centerY: number = (target.clientHeight - radius * 2) / 2
            const client_xy: ObjectType = getClientXY(event) || {}
            const x: number = client_xy.x - left - radius
            const y: number = client_xy.y - top - radius
            if (typeof cb === 'function') {
                cb({ x, y, centerX, centerY, size, target })
            } else {
                const ripple: HTMLDivElement = document.createElement('div')
                ripple.className =
                    'ripple absolute top-0 left-0 rounded-full pointer-events-none bg-current opacity-0 z-100'
                ripple.style.transform = `translate(${x}px, ${y}px) scale3d(0.3, 0.3, 0.3)`
                ripple.style.width = `${size}px`
                ripple.style.height = `${size}px`
                ripple.dataset.createdAt = String(performance.now())
                ripple.dataset.pointX = `${x}px`
                ripple.dataset.pointY = `${y}px`
                target.appendChild(ripple)
                window.setTimeout(() => {
                    ripple.style.transform = `translate(${centerX}px, ${centerY}px) scale3d(1, 1, 1)`
                    ripple.style.opacity = '0.25'
                }, 20)
            }
        } catch (error) {
            console.warn(error)
        }
    }

/**
 * 删除ripple节点
 */
export const removeRipple = () => {
    try {
        const ripples = document.querySelectorAll('.ripple')
        if (!ripples.length) {
            return
        }
        for (let i = 0, len = ripples.length; i < len; i++) {
            const ripple = ripples[i] as HTMLElement
            const delay: number = 250 - performance.now() + Number(ripple.dataset.createdAt)
            let timer = window.setTimeout(() => {
                ripple.style.opacity = '0'
                clearTimeout(timer)
                timer = window.setTimeout(() => {
                    if (ripple && ripple.parentNode) {
                        ripple.parentNode.removeChild(ripple)
                    }
                    clearTimeout(timer)
                }, 250)
            }, delay)
        }
    } catch (error) {
        console.warn(error)
    }
}

/**
 * 按照浏览器帧率节流函数执行次数
 * @param {FrameRequestCallback} fn 动画执行的函数
 * @returns {number}
 */
export const requestAnimationFrame = (fn: FrameRequestCallback): number =>
    window.requestAnimationFrame ? window.requestAnimationFrame(fn) : window.setTimeout(fn, 16)

/**
 * 获取根节点(传入的rootId获取不到时，直接获取body元素)
 * @param {string} rootId 要获取的节点的id名称
 * @returns {HTMLElement} 根节点
 */
export const getRoot = (rootId = 'root') => document.getElementById(rootId) || document.body

/**
 * 元素拖拽时的最大水平/垂直距离
 * @param n 拖拽的元素
 * @returns {ElementBoundary}
 */
export const getEleBoundary = (n: HTMLElement): ElementBoundary => {
    let maxX = 0
    let maxY = 0
    let left = 0
    let top = 0
    if (n) {
        const pte = n.parentElement
        if (pte) {
            maxX = pte.offsetWidth - n.offsetWidth
            maxY = pte.offsetHeight - n.offsetHeight
            left =
                pte.offsetLeft + (document.documentElement.scrollLeft || document.body.scrollLeft)
            top = pte.offsetTop + (document.documentElement.scrollTop || document.body.scrollTop)
        }
    }
    return {
        maxX,
        maxY,
        left,
        top
    }
}

/**
 * canvas 绘制圆角
 * @param {CanvasRenderingContext2D} ctx 绘制对象
 * @param {number} x 图形左顶点 x 坐标
 * @param {number} y 图形左顶点 y 坐标
 * @param {number} w 图形宽度
 * @param {number} h 图形高度
 * @param {number} r 半径
 */
export const drawRoundedRect = (
    ctx: CanvasRenderingContext2D,
    x: number,
    y: number,
    w: number,
    h: number,
    r: number
): void => {
    if (typeof (ctx as any).roundRect === 'function') {
        ;(ctx as any).roundRect(x, y, w, h, r)
    } else {
        ctx.beginPath()
        ctx.moveTo(x + r, y)
        ctx.lineTo(x + w - r, y)
        ctx.quadraticCurveTo(x + w, y, x + w, y + r)
        ctx.lineTo(x + w, y + h - r)
        ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h)
        ctx.lineTo(x + r, y + h)
        ctx.quadraticCurveTo(x, y + h, x, y + h - r)
        ctx.lineTo(x, y + r)
        ctx.quadraticCurveTo(x, y, x + r, y)
        ctx.closePath()
    }
}

/**
 * canvas 画圆角的图片
 * @param {CanvasRenderingContext2D} ctx canvas绘画上下文
 * @param {HTMLImageElement} img 图片节点
 * @param {number} x 图片在画布 x 轴坐标
 * @param {number} y 图片在画布 y 轴坐标
 * @param {number} w 图片宽度
 * @param {number} h 图片高度
 * @param {number} r 图片圆角弧度
 */
export const canvasRadiusImg = (
    ctx: CanvasRenderingContext2D,
    img: HTMLImageElement,
    x: number,
    y: number,
    w: number,
    h: number,
    r = 0
) => {
    if (r !== 0) {
        ctx.save()
        drawRoundedRect(ctx, x, y, w, h, r)
        ctx.clip()
        ctx.drawImage(img, x, y, w, h)
        ctx.restore()
    } else {
        ctx.drawImage(img, x, y, w, h)
    }
}

/**
 * 检测 css 属性兼容性
 * @param {string} cssKey css 属性名 -webkit-line-clamp
 * @param {string} cssValue css 属性值 2
 * @returns {boolean}
 */
export const isSupportCssKey = (cssKey = '-webkit-line-clamp', cssValue = '2'): boolean => {
    if (window.CSS && typeof window.CSS.supports === 'function') {
        return CSS.supports(cssKey, cssValue)
    }
    return false
}

/** 获取节点计算后的样式 */
export const getElementStyle = (n: HTMLElement) => {
    if (window.getComputedStyle) {
        return window.getComputedStyle(n)
    }
    return (n as any).currentStyle
}

/**
 * @description 获取某个节点下的所有子节点
 * @param {HTMLElement[]} n 目标节点
 * @returns {HTMLElement[]} 所有子节点
 */
export const getAllChildNodes = (n: HTMLElement[]): HTMLElement[] => {
    const newList: HTMLElement[] = []
    if (n) {
        const fn = (_nodes: any) => {
            for (let i = 0, len = _nodes.length; i < len; i++) {
                const child = _nodes[i] || {}
                if (child.nodeType === 1) {
                    newList.push(child)
                }
                if (child.childNodes.length) {
                    fn(child.childNodes)
                }
            }
        }
        fn(n)
    }
    return newList
}
/** 动画时长 */
export const ANIMATION_TIME = 300

/** 淡入动画效果 */
export const APPEAR_ANIMATION = [{ opacity: 0 }, { opacity: 1 }]

/** 淡出动画效果 */
export const DISAPPEAR_ANIMATION = [{ opacity: 1 }, { opacity: 0 }]

/** 构造函数，可在原型上添加方法 */
export function AnimateFn() {}
AnimateFn.prototype.onfinish = function () {}
AnimateFn.prototype.animateFn = function (
    _e: HTMLElement,
    _animate: ObjectType[],
    _durtion: number = ANIMATION_TIME
) {
    const vals: number[] = []
    const keys = Object.keys(_animate[0])
    keys.forEach((key) => {
        vals.push(parseInt(_animate[1][key]) - parseInt(_animate[0][key]))
    })
    let step = 0
    let inter: any = null
    inter = setInterval(() => {
        step += 50
        let rate = step / _durtion
        rate = rate >= 1 ? 1 : rate
        keys.forEach((key, i) => {
            _e.style.cssText += `${key}:${vals[i] * rate};`
        })
        if (rate >= 1) {
            this.onfinish()
            clearInterval(inter)
            inter = null
        }
    }, 50)
    return this
}

/**
 * 添加元素动画效果
 * @param {HTMLElement} _e 元素
 * @param {ObjectType[]} _animate 动画
 * @param {number} _durtion 动画时长
 */
export const animateFn = new (AnimateFn as any)().animateFn

/**
 * 添加元素动画效果
 * @param {HTMLElement} _e 元素
 * @param {ObjectType[]} _animate 动画
 * @param {number} _durtion 动画时长
 */
export const comAnimate = (
    _e: HTMLElement,
    _animate: ObjectType[],
    _durtion: number = ANIMATION_TIME
) => {
    if (typeof HTMLElement.prototype.animate === 'function') {
        return _e.animate(_animate, _durtion)
    }
    return animateFn(_e, _animate, _durtion)
}

/**
 * 创建元素插入到 body 中
 * @param {HTMLElement} dom 要插入到 body 的元素
 * @param {boolean} isFullScreen 添加滚动条
 */
export const insertElement = (n: HTMLElement, isFullScreen?: boolean) => {
    n.animate(APPEAR_ANIMATION, ANIMATION_TIME)
    // 滚动条隐藏
    let ste: HTMLStyleElement | null = null
    if (isFullScreen) {
        ste = document.createElement('style')
        ste.textContent = 'html body { overflow-y: hidden }'
        ste.setAttribute('data-key', 'body-overflow-hidden')
        document.head.appendChild(ste)
    }
    const contians = getRoot()

    contians.appendChild(n)
    return (_fn?: (_p?: unknown) => void) => {
        n.animate(DISAPPEAR_ANIMATION, ANIMATION_TIME).onfinish = () => {
            if (isFullScreen && ste) document.head.removeChild(ste)
            if (contians.contains(n)) {
                contians.removeChild(n)
            }
            if (_fn) {
                _fn()
            }
        }
    }
}

/**
 * 获取页面滚动卷起的顶部距离
 * @param {Element | Window} el
 * @returns {number}
 */
export function getScrollTop(el: Element | Window): number {
    const top =
        'scrollTop' in el
            ? el.scrollTop
            : window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop

    // iOS scroll bounce cause minus scrollTop
    return Math.max(top, 0)
}

/**
 * 获取页面滚动卷起的左边距离
 * @param {Element | Window} el
 * @returns {number}
 */
export const getScrollLeft = (el: Element | Window): number => {
    const left =
        'scrollLeft' in el
            ? el.scrollLeft
            : window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft

    return Math.max(left, 0)
}

/**
 * 滚动到指定位置的方法
 * @param {HTMLElement | Window} el
 * @param {ScrollToOptions}
 * @returns {Promise}
 */
export function scrollTo(
    el: HTMLElement | Window,
    { top = 0, left = 0, duration = 300, animation = (_v: number): number => _v }: ScrollToOptions
): Promise<void> {
    const startTime = Date.now()
    const scrollTop = getScrollTop(el)
    const scrollLeft = getScrollLeft(el)
    return new Promise((resolve) => {
        const frame = () => {
            const progress = (Date.now() - startTime) / duration

            if (progress < 1) {
                const nextTop = scrollTop + (top - scrollTop) * animation(progress)
                const nextLeft = scrollLeft + (left - scrollLeft) * animation(progress)

                el.scrollTo(nextLeft, nextTop)
                requestAnimationFrame(frame)
            } else {
                el.scrollTo(left, top)
                resolve()
            }
        }
        requestAnimationFrame(frame)
    })
}

/**
 * css 类名处理
 * @param {string[]} _cls css 类名数组
 * @param {string} _cn 其他 css 样式类名
 * @returns {string} 处理后的最终样式
 */
export const classEsy = (_cls?: string[], _cn?: string): string => {
    let csn = ''
    if (Array.isArray(_cls) && _cls.length) {
        _cls.forEach((str) => {
            if (str) {
                csn = csn ? `${csn} ${str}` : str
            }
        })
    }
    return _cn ? `${csn} ${_cn}` : csn
}

/**
 * 处理 css 样式拼接
 * @param {string} _prefix 前缀
 * @param {string} _mainName 大类别名称
 * @param {string[]} _otherArr 其他的样式切片
 * @param {string} _cn 其他样式
 * @returns {string} 处理后的样式
 */
export function classBem(
    _prefix: string,
    _mainName: string,
    _otherArr?: string[],
    _cn?: string
): string {
    const name = `${_prefix}-${_mainName}`
    let cls = name
    if (Array.isArray(_otherArr) && _otherArr.length) {
        _otherArr.forEach((st) => {
            if (st) {
                cls = `${cls} ${name}-${st}`
            }
        })
    }
    return _cn ? `${cls} ${_cn}` : cls
}

/**
 * 移动端适配
 * @param {number} fs 375屏幕的文字大小
 */
export const initRem = (fs = 16) => {
    const setRem = () => {
        const scale = document.documentElement.clientWidth / 375
        const fontsize = fs * Math.min(scale, 3)
        document.documentElement.style.fontSize = `${fontsize}px`
    }
    setRem()
    window.onresize = () => {
        setRem()
    }
}

/**
 * 获取监听元素回调函数绑定key
 * @param {string} _k
 * @param {string | number | null} id
 * @returns {string}
 */
export const getMapObsKey = (_k: string, _id?: string | number | null) =>
    _id ? `${_k}-${_id}` : _k

/** 配合 IntersectionObserver 使用 */
export const viewClientMap = new Map()

/** 创建一个交叉观察者实例 */
export const observerIns = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
        const dId = entry.target.getAttribute('data-id')
        // 检查元素是否在视窗内
        if (entry.isIntersecting) {
            const fn = viewClientMap.get(getMapObsKey('show', dId))
            if (typeof fn === 'function') {
                fn(entry)
            }
        } else {
            const fn = viewClientMap.get(getMapObsKey('hidden', dId))
            if (typeof fn === 'function') {
                fn(entry)
            }
        }
    })
})

/**
 *  设置元素图片路径
 * @param {HTMLImageElement | HTMLElement} _n img/div元素
 * @returns {void}
 */
export const setImgSrc = (_n: HTMLImageElement | HTMLElement) => {
    const url = _n.getAttribute('data-src')
    if (!url) return
    if (_n.nodeName.toLocaleLowerCase() === 'div') {
        // 背景图设置
        _n.style.cssText = _n.style.cssText.replace(
            /background-image.*(;)?/,
            `background-image:url(${url})`
        )
    } else {
        _n.setAttribute('src', url)
    }
    _n.setAttribute('data-src', '')
}

/**
 * 判断该节点是否进入可视区域
 * @param {HTMLImageElement | HTMLElement} _el 元素
 * @param {number} distance 顶部距离 默认10px
 * @returns {number}
 */
export const isInView = (_el: HTMLImageElement | HTMLElement, distance: number = 10) => {
    // 获取视窗的高度 图片距离可视区域10单位 就开始加载
    const clientHeight = window.innerHeight + distance
    // 使用 getBoundingClientRect API
    if (_el.getBoundingClientRect) {
        const bound = _el.getBoundingClientRect()
        console.warn(bound.top <= clientHeight, { bound, clientHeight })
        // 如果只考虑向下滚动加载
        return bound.top <= clientHeight
    }
    // 获取文档滚动的高度
    const st = document.documentElement.scrollTop || document.body.scrollTop

    return clientHeight + st > _el.offsetTop
}

/** 图片元素是否支持loading属性 */
export const isImgLoading = () => 'loading' in HTMLImageElement.prototype

/** 浏览器是否支持 IntersectionObserver API */
export const isIntersectionObserver = () => typeof IntersectionObserver === 'function'

/**
 * IntersectionObserver 处理图片/背景图懒加载
 * @param {HTMLImageElement | HTMLElement} _el
 */
export const imgLazyload = (_el: HTMLImageElement | HTMLElement) => {
    if (isIntersectionObserver()) {
        const dId = _el.getAttribute('data-id')
        const obserKey = `show-${dId}`
        viewClientMap.set(obserKey, (entry: any) => {
            const img = entry.target
            if (entry.intersectionRatio > 0 && entry.intersectionRatio <= 1) {
                setImgSrc(img)
                // 移出监听
                observerIns.unobserve(img)
                viewClientMap.delete(obserKey)
            }
        })
        observerIns.observe(_el)
    }
}

/**
 * 原生scroll/touchmove + getBoundingClientRect 实现图片懒加载
 * @param {Function} _fn 事件的回调
 * @param {number} _t 滚动时的时间间隔
 * @param {number} _d 距离顶部多少距离时加载图片
 * @returns {Function} 移除监听的函数
 */
export const imgLazyloadScroll = (_fn?: Function, _t: number = 150, _d: number = 10): Function => {
    if (isIntersectionObserver() || isImgLoading()) return () => {}
    function lazyLoadImgs(e: any) {
        if (typeof _fn === 'function') {
            _fn(e)
        }
        const imgs = document.querySelectorAll('[data-id]')
        for (let i = 0; i < imgs.length; i++) {
            const _el = imgs[i]
            const dId = _el.getAttribute('data-id')
            // 如果加载过就跳过
            if (!dId || (_el as any).isLoad) return
            // 判断图片进入可视区域做赋值操作
            if (isInView(_el as any, _d)) {
                setImgSrc(_el as any)
                ;(_el as any).isLoad = true
            }
        }
    }
    const reLazyLoadImg = throttle(lazyLoadImgs, _t)
    reLazyLoadImg()
    window.addEventListener(isMobile() ? 'touchmove' : 'scroll', reLazyLoadImg)
    return () => {
        window.removeEventListener(isMobile() ? 'touchmove' : 'scroll', reLazyLoadImg)
    }
}
